/*
 * Copyright (c) 2023 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "se_napi_oma_common.h"

#include <string>
#include <securec.h>
namespace OHOS {
namespace SE {
namespace OMAPI {

std::string NapiSEOmaCommon::keyCode = "code";
std::string NapiSEOmaCommon::sePermDesc = "ohos.permission.MANAGE_SECURE_SETTINGS";
const std::map<JSErrorCode, std::string> seErrorInfoMap {
    {BUSI_ERR_PARAM_EXCEPTION, "The parameter check failed."},
    {BUSI_ERR_CAPABILITY_EXCEPTION, "Capability not supported."},
    {BUSI_ERR_SE_STATE_EXCEPTION, "IllegalStateError, an attempt is made to use an SE session that has been closed."},
    {BUSI_ERR_SE_ELEMENT_EXCEPTION, "NoSuchElementError, the AID on the SE is not available or cannot be selected."},
    {BUSI_ERR_SE_ACCESS_EXCEPTION, "SecurityError, the calling application cannot be granted access to this AID \
or the default applet on this session."},
    {BUSI_ERR_SE_IO_EXCEPTION, "IOError, there is a communication problem to the reader or the SE."}
};

template<typename T>
napi_value NapiSEOmaCommon::JSOmaClassConstructor(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
    T* omaJSClass = new T();

    napi_status status = napi_wrap(
        env,
        thisVar,
        omaJSClass,
        [](napi_env env, void *data, void *hint) {
            T *service = (T *)data;
            if (service) {
                delete service;
                service = nullptr;
            }
        },
        nullptr,
        nullptr);
    if (status != napi_ok) {
        if (omaJSClass != nullptr) {
            delete omaJSClass;
            omaJSClass = nullptr;
        }
        ErrorLog("napi_wrap failed, object is null. status = %{public}d", status);
        return NapiSEOmaCommon::CreateUndefined(env);
    }
    return thisVar;
}

napi_value NapiSEOmaCommon::CreateUndefined(napi_env env)
{
    napi_value result = nullptr;
    NAPI_CALL(env, napi_get_undefined(env, &result));
    return result;
}

void ThrowAsyncError(const napi_env &env, AsyncContext *asyncContext, int errCode, const std::string &errMsg,
    const std::string &errData)
{
    if (asyncContext == nullptr) {
        ErrorLog("asyncContext is nullptr");
        return;
    }
    napi_value businessError = NapiSEOmaCommon::GenerateBusinessError(env, errCode, errMsg, errData);
    if (asyncContext->callbackRef != nullptr) {
        ErrorLog("ThrowAsyncError for callback");
        napi_value recv = NapiSEOmaCommon::CreateUndefined(env);
        napi_value callbackFunc = nullptr;
        NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, asyncContext->callbackRef, &callbackFunc));
        napi_value callbackValues[] = {nullptr, nullptr};
        callbackValues[0] = businessError; // parameter "error"
        callbackValues[1] = NapiSEOmaCommon::CreateUndefined(env); // parameter "callback"
        napi_value result = nullptr;
        NAPI_CALL_RETURN_VOID(
            env, napi_call_function(env, recv, callbackFunc, std::size(callbackValues), callbackValues, &result));
        NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, asyncContext->callbackRef));
    } else if (asyncContext->deferred != nullptr) {
        ErrorLog("ThrowAsyncError for promise, errCode = %{public}d", errCode);
        NAPI_CALL_RETURN_VOID(env, napi_reject_deferred(env, asyncContext->deferred, businessError));
    }

    if (asyncContext->work != nullptr) {
        napi_delete_async_work(env, asyncContext->work);
    }
    delete asyncContext;
    asyncContext = nullptr;
}

int BuildOutputErrorCode(int errCode)
{
    if (errCode == SE_ILLEGAL_STATE_ERROR) {
        return BUSI_ERR_SE_STATE_EXCEPTION;
    } else if (errCode == SE_NO_SUCH_ELEMENT_ERROR) {
        return BUSI_ERR_SE_ELEMENT_EXCEPTION;
    } else if (errCode == SE_IO_ERROR) {
        return BUSI_ERR_SE_IO_EXCEPTION;
    } else if (errCode == SE_ACCESS_CONTROL_ERROR) {
        return BUSI_ERR_SE_ACCESS_EXCEPTION;
    } else if (errCode == BUSI_ERR_CAPABILITY_EXCEPTION) {
        return BUSI_ERR_CAPABILITY_EXCEPTION;
    } else if (errCode >= SE_NULL_POINTER_ERROR && errCode <= SE_IPC_TOKEN_ERROR) {
        return BUSI_ERR_SE_STATE_EXCEPTION;
    }
    return ERR_SUCCESS;
}

std::string NapiSEOmaCommon::BuildErrorMessage(int errCode, std::string funcName, std::string forbiddenPerm,
    std::string paramName, std::string expertedType)
{
    std::string errMsg;
    if (errCode == BUSI_ERR_PERM_EXCEPTION) {
        return errMsg.append("Permission denied. An attempt was made to ${")
            .append(funcName)
            .append("} forbidden by permission: ${")
            .append(forbiddenPerm)
            .append("}.");
    }
    JSErrorCode code = static_cast<JSErrorCode>(errCode);
    auto it = seErrorInfoMap.find(code);
    if (it != seErrorInfoMap.end()) {
        return it->second;
    } else {
        return "Unknown error message";
    }
}

napi_value NapiSEOmaCommon::GenerateBusinessError(const napi_env &env, int errCode, const std::string &errMessage,
    const std::string &errData)
{
    napi_value code = nullptr;
    napi_create_uint32(env, errCode, &code);
    napi_value message = nullptr;
    napi_create_string_utf8(env, errMessage.c_str(), NAPI_AUTO_LENGTH, &message);
    napi_value data = nullptr;
    napi_create_string_utf8(env, errData.c_str(), NAPI_AUTO_LENGTH, &data);
    napi_value businessError = nullptr;
    napi_create_error(env, nullptr, message, &businessError);
    napi_set_named_property(env, businessError, keyCode.c_str(), code);
    napi_set_named_property(env, businessError, "data", data);
    return businessError;
}

bool NapiSEOmaCommon::CheckSEStatusCodeAndThrow(const napi_env &env, int errCode, const std::string &funcName)
{
    errCode = BuildOutputErrorCode(errCode);
    if (errCode == ErrorCode::SE_SUCCESS) {
        return true;
    } else if (errCode == BUSI_ERR_PERM_EXCEPTION) {
        napi_throw(env, GenerateBusinessError(env, errCode,
            BuildErrorMessage(BUSI_ERR_PERM_EXCEPTION, funcName, sePermDesc, EMPTY_STR, EMPTY_STR), ""));
        return false;
    } else if (errCode == BUSI_ERR_PARAM_EXCEPTION) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION,
            BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION, funcName, EMPTY_STR, EMPTY_STR, EMPTY_STR), ""));
        return false;
    } else if (errCode == BUSI_ERR_CAPABILITY_EXCEPTION) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_CAPABILITY_EXCEPTION,
            BuildErrorMessage(BUSI_ERR_CAPABILITY_EXCEPTION, funcName, EMPTY_STR, EMPTY_STR, EMPTY_STR), ""));
        return false;
    } else if (errCode == SE_ILLEGAL_STATE_ERROR) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_SE_STATE_EXCEPTION,
            BuildErrorMessage(BUSI_ERR_SE_STATE_EXCEPTION, EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR), ""));
        return false;
    } else if (errCode == ErrorCode::SE_NO_SUCH_ELEMENT_ERROR) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_SE_ELEMENT_EXCEPTION,
            BuildErrorMessage(BUSI_ERR_SE_ELEMENT_EXCEPTION, EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR), ""));
        return false;
    } else if (errCode == ErrorCode::SE_IO_ERROR) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_SE_IO_EXCEPTION,
            BuildErrorMessage(BUSI_ERR_SE_IO_EXCEPTION, EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR), ""));
        return false;
    } else if (errCode >= ErrorCode::SE_NULL_POINTER_ERROR) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_SE_STATE_EXCEPTION,
            BuildErrorMessage(BUSI_ERR_SE_STATE_EXCEPTION, EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR), ""));
        return false;
    }
    return false;
}

void NapiSEOmaCommon::CheckArgCountAndThrow(const napi_env &env, int argCount, int expCount)
{
    if (argCount != expCount) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION, BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION,
            EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR), ""));
    }
}

bool NapiSEOmaCommon::CheckArrayNumberAndThrow(const napi_env &env,
                                               const napi_value &param,
                                               const std::string &argName,
                                               const std::string &argType)
{
    if (!IsNumberArray(env, param)) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION, BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION,
            EMPTY_STR, EMPTY_STR, argName, argType), ""));
        return false;
    }
    return true;
}

bool NapiSEOmaCommon::CheckStringAndThrow(const napi_env &env,
                                          const napi_value &param,
                                          const std::string &argName,
                                          const std::string &argType)
{
    if (!IsTargetValueType(env, param, napi_valuetype::napi_string)) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION, BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION,
            EMPTY_STR, EMPTY_STR, argName, argType), ""));
        return false;
    }
    return true;
}

void NapiSEOmaCommon::ThrowParameterError(const napi_env &env)
{
    napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION, BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION,
        EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR), ""));
}

bool NapiSEOmaCommon::CheckNumberAndThrow(const napi_env &env,
                                          const napi_value &param,
                                          const std::string &argName,
                                          const std::string &argType)
{
    if (!IsTargetValueType(env, param, napi_valuetype::napi_number)) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION, BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION,
            EMPTY_STR, EMPTY_STR, argName, argType), ""));
        return false;
    }
    return true;
}

bool NapiSEOmaCommon::IsNumberArray(const napi_env &env, const napi_value &param)
{
    if (!IsArray(env, param)) {
        return false;
    }

    uint32_t arrayLength = 0;
    napi_get_array_length(env, param, &arrayLength);
    napi_value elementValue = nullptr;
    for (uint32_t i = 0; i < arrayLength; ++i) {
        napi_get_element(env, param, i, &elementValue);
        napi_valuetype elementType = napi_undefined;
        napi_typeof(env, elementValue, &elementType);
        if (elementType != napi_number) {
            return false;
        }
    }
    return true;
}

bool NapiSEOmaCommon::IsObjectArray(const napi_env &env, const napi_value &param)
{
    if (!IsArray(env, param)) {
        return false;
    }

    uint32_t arrayLength = 0;
    napi_get_array_length(env, param, &arrayLength);
    napi_value elementValue = nullptr;
    for (uint32_t i = 0; i < arrayLength; ++i) {
        napi_get_element(env, param, i, &elementValue);
        napi_valuetype elementType = napi_undefined;
        napi_typeof(env, elementValue, &elementType);
        if (elementType != napi_object) {
            return false;
        }
    }
    return true;
}

bool NapiSEOmaCommon::IsArray(const napi_env &env, const napi_value &param)
{
    bool arrayType = false;
    napi_status status = napi_is_array(env, param, &arrayType);
    if (status != napi_ok || !arrayType) {
        ErrorLog("para type is not array");
        return false;
    }

    uint32_t arrayLength = 0;
    napi_get_array_length(env, param, &arrayLength);
    if (arrayLength == 0) {
        ErrorLog("array length can't be 0");
        return false;
    }
    return true;
}

bool NapiSEOmaCommon::IsTargetValueType(const napi_env &env, const napi_value &param, napi_valuetype targetType)
{
    napi_valuetype valueType = napi_undefined;
    napi_typeof(env, param, &valueType);
    return valueType == targetType;
}

bool NapiSEOmaCommon::ParseBytesVector(napi_env env, std::vector<unsigned char> &vec, napi_value args)
{
    bool isArray = false;
    napi_status status = napi_is_array(env, args, &isArray);
    if (status != napi_ok || !isArray) {
        ErrorLog("ParseBytesVector, not array");
        return false;
    }
    uint32_t arrayLength = 0;
    napi_get_array_length(env, args, &arrayLength);
    for (uint32_t i = 0; i < arrayLength; i++) {
        napi_value element = nullptr;
        napi_get_element(env, args, i, &element);

        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, element, &valueType);
        if (valueType != napi_number) {
            ErrorLog("ParseBytesVector, not number!");
            return false;
        }

        uint32_t byteValue = 0x0;
        napi_get_value_uint32(env, element, &byteValue);
        vec.push_back(static_cast<unsigned char>(byteValue));
    }
    return true;
}

void NapiSEOmaCommon::Int32VectorToJS(napi_env env, std::vector<int32_t>& src, napi_value &result)
{
    if (src.empty()) {
        ErrorLog("BytesVectorToJS src empty");
        napi_create_array_with_length(env, 0, &result);
        return;
    }
    size_t idx = 0;
    napi_create_array_with_length(env, src.size(), &result);
    for (auto& num : src) {
        napi_value obj = nullptr;
        napi_create_int32(env, num, &obj);
        napi_set_element(env, result, idx, obj);
        idx++;
    }
}

napi_value NapiSEOmaCommon::GetNamedProperty(napi_env env, napi_value object, const std::string &propertyName)
{
    napi_value value = nullptr;
    NAPI_CALL(env, napi_get_named_property(env, object, propertyName.c_str(), &value));
    return value;
}

napi_valuetype NapiSEOmaCommon::GetValueType(napi_env env, napi_value value)
{
    if (value == nullptr) {
        return napi_undefined;
    }

    napi_valuetype valueType = napi_undefined;
    NAPI_CALL_BASE(env, napi_typeof(env, value, &valueType), napi_undefined);
    return valueType;
}

std::string NapiSEOmaCommon::GetStringFromValue(napi_env env, napi_value value)
{
    constexpr int32_t maxTextLength = 4096;
    char msgChars[maxTextLength] = {0};
    size_t msgLength = 0;
    NAPI_CALL_BASE(env, napi_get_value_string_utf8(env, value, msgChars, maxTextLength, &msgLength), EMPTY_STR);
    if (msgLength > 0) {
        return std::string(msgChars, 0, msgLength);
    } else {
        return EMPTY_STR;
    }
}

void NapiSEOmaCommon::ConvertStringToNumberArray(napi_env env, napi_value &result, std::string srcValue)
{
    if (srcValue.empty()) {
        WarnLog("ConvertStringToNumberArray srcValue empty");
        napi_create_array_with_length(env, 0, &result);
        return;
    }
    uint32_t strLength = srcValue.length();
    if (strLength % HEX_BYTE_LEN != 0) {
        srcValue = '0' + srcValue;
        strLength++;
    }

    napi_create_array_with_length(env, (strLength / HEX_BYTE_LEN), &result);
    unsigned int srcIntValue;
    for (uint32_t i = 0; i < strLength; i += HEX_BYTE_LEN) {
        // parse the hex string bytes into array.
        std::string oneByte = srcValue.substr(i, HEX_BYTE_LEN);
        if (sscanf_s(oneByte.c_str(), "%x", &srcIntValue) <= 0) {
            ErrorLog("ConvertStringToNumberArray, sscanf_s failed.");
            return;
        }
        unsigned char hexByte = static_cast<unsigned char>(srcIntValue & 0xFF);
        napi_value hexByteValue = nullptr;
        napi_create_int32(env, hexByte, &hexByteValue);
        napi_set_element(env, result, (i / HEX_BYTE_LEN), hexByteValue);
    }
}

napi_value NapiSEOmaCommon::HandleAsyncWork(napi_env env, AsyncContext *context, const std::string &workName,
    napi_async_execute_callback execute, napi_async_complete_callback complete)
{
    DebugLog("HandleAsyncWork start workName = %{public}s", workName.c_str());
    napi_value result = nullptr;
    if (context->callbackRef == nullptr) {
        napi_create_promise(env, &context->deferred, &result);
    } else {
        napi_get_undefined(env, &result);
    }
    napi_value resource = NapiSEOmaCommon::CreateUndefined(env);
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, workName.data(), NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, resource, resourceName, execute, complete, static_cast<void *>(context),
        &context->work);
    napi_queue_async_work(env, context->work);
    DebugLog("Session::HandleAsyncWork success");
    return result;
}

bool NapiSEOmaCommon::ParseString(napi_env env, std::string &value, napi_value args)
{
    napi_valuetype type = napi_undefined;
    if (args == nullptr) {
        ErrorLog("args is nullptr!");
        value = "";
        return false;
    }
    napi_typeof(env, args, &type);
    if (type != napi_string) {
        ErrorLog("value not STRING type.");
        value = "";
        return false;
    }

    size_t length = 0;
    if (napi_get_value_string_utf8(env, args, nullptr, 0, &length) != napi_ok) {
        ErrorLog("fail to get string length.");
        value = "";
        return false;
    }

    value.reserve(length + 1);
    value.resize(length);

    if (napi_get_value_string_utf8(env, args, value.data(), (length + 1), &length) != napi_ok) {
        ErrorLog("fail to get string value.");
        value = "";
        return false;
    }

    return true;
}

void DoAsyncCallbackOrPromise(const napi_env &env, AsyncContext *asyncContext, napi_value callbackValue)
{
    if (asyncContext->callbackRef != nullptr) {
        InfoLog("DoAsyncCallbackOrPromise for callback");
        constexpr int RESULT_PARAMS_NUM = 2;
        // get callback function
        napi_value result = nullptr;
        napi_value callbackFunc = nullptr;
        NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, asyncContext->callbackRef, &callbackFunc));
        napi_value callbackValues[RESULT_PARAMS_NUM];
        callbackValues[1] = callbackValue;
        if (asyncContext->resolved) {
            callbackValues[0] = NapiSEOmaCommon::CreateUndefined(env);
            NAPI_CALL_RETURN_VOID(
                env, napi_call_function(env, nullptr, callbackFunc, RESULT_PARAMS_NUM, callbackValues, &result));
            NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, asyncContext->callbackRef));
        } else {
            int errCode = BuildOutputErrorCode(asyncContext->errorCode);
            std::string errMsg = NapiSEOmaCommon::BuildErrorMessage(errCode,
                "DoAsyncCallbackOrPromise", "", "", "");
            callbackValues[0] = NapiSEOmaCommon::GenerateBusinessError(env, errCode, errMsg, "");
            NAPI_CALL_RETURN_VOID(
                env, napi_call_function(env, nullptr, callbackFunc, RESULT_PARAMS_NUM, callbackValues, &result));
            NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, asyncContext->callbackRef));
        }
    } else if (asyncContext->deferred != nullptr) {
        if (asyncContext->resolved) {
            NAPI_CALL_RETURN_VOID(env, napi_resolve_deferred(env, asyncContext->deferred, callbackValue));
            InfoLog("DoAsyncCallbackOrPromise for promise notify js succ");
        } else {
            int errCode = BuildOutputErrorCode(asyncContext->errorCode);
            std::string errMsg = NapiSEOmaCommon::BuildErrorMessage(errCode, "", "", "", "");
            napi_value businessError = NapiSEOmaCommon::GenerateBusinessError(env, errCode, errMsg, "");
            NAPI_CALL_RETURN_VOID(env, napi_reject_deferred(env, asyncContext->deferred, businessError));
            ErrorLog("DoAsyncCallbackOrPromise for promise throw(%{public}d)", errCode);
        }
    }

    napi_delete_async_work(env, asyncContext->work);
    delete asyncContext;
    asyncContext = nullptr;
}
} // namespace OMAPI
}  // namespace SE
}  // namespace OHOS
